🐍 Python Dictionary Tutorial

A comprehensive guide to mastering Python dictionaries

1. Creating Dictionaries

Introduction: Dictionaries are one of Python's most fundamental data structures. They store data in key-value pairs, allowing you to quickly retrieve values using their associated keys. Think of a dictionary like a real-world dictionary where words (keys) map to their definitions (values). Unlike lists that use numeric indices, dictionaries use meaningful keys, making your code more readable and efficient.

Dictionaries are mutable, unordered collections of key-value pairs. They are defined using curly braces {} or the dict() constructor. The keys must be unique and immutable (strings, numbers, or tuples), while values can be of any data type.

Empty Dictionary

# Method 1: Using curly braces
empty_dict = {}

# Method 2: Using dict() constructor
empty_dict = dict()

Dictionary with Initial Values

# Simple dictionary
student = {
    "name": "Alice",
    "age": 20,
    "grade": "A"
}

# Using dict() constructor
student = dict(name="Alice", age=20, grade="A")

# From list of tuples
items = [("name", "Alice"), ("age", 20), ("grade", "A")]
student = dict(items)
Note: Dictionary keys must be immutable (strings, numbers, tuples). Values can be any data type.

2. Accessing Dictionary Elements

Introduction: Once you've created a dictionary, you need to retrieve the data stored in it. Python provides multiple ways to access dictionary values, each with its own advantages. Understanding when to use each method is crucial for writing robust, error-free code. The key difference between methods is how they handle missing keys - some raise errors, while others provide safe fallbacks.

You can access dictionary values using keys in several ways. The most common methods are using square brackets or the get() method. Square brackets are direct but raise errors for missing keys, while get() provides a safer alternative with optional default values.

Using Square Brackets

student = {"name": "Alice", "age": 20, "grade": "A"}

# Access value
print(student["name"])  # Output: Alice

# This raises KeyError if key doesn't exist
# print(student["address"])  # KeyError!

Using get() Method

# Safer way - returns None if key doesn't exist
print(student.get("name"))      # Output: Alice
print(student.get("address"))   # Output: None

# With default value
print(student.get("address", "Not Available"))  # Output: Not Available

Checking Key Existence

if "name" in student:
    print(student["name"])

# Check if key doesn't exist
if "address" not in student:
    print("Address not found")

3. Updating Dictionaries

Introduction: Dictionaries are mutable, meaning you can modify them after creation. This flexibility makes dictionaries perfect for representing data that changes over time, such as user profiles, configuration settings, or counters. You can add new key-value pairs, modify existing values, or remove items entirely. Understanding these operations is essential for managing dynamic data in your programs.

Since dictionaries are mutable, you can add, modify, and delete elements after creation. Python provides several methods for these operations, each suited for different scenarios. You can update single items directly or use built-in methods for more complex modifications.

Adding/Modifying Elements

student = {"name": "Alice", "age": 20}

# Add new key-value pair
student["grade"] = "A"

# Modify existing value
student["age"] = 21

print(student)
# Output: {'name': 'Alice', 'age': 21, 'grade': 'A'}

Removing Elements

# Using del keyword
del student["grade"]

# Using pop() - returns the value
age = student.pop("age")
print(age)  # Output: 21

# Using pop() with default value
address = student.pop("address", "Not found")

# Remove last inserted item (Python 3.7+)
item = student.popitem()

# Clear all items
student.clear()

4. Dictionary Methods

Introduction: Python dictionaries come with a rich set of built-in methods that make data manipulation powerful and convenient. These methods allow you to iterate over keys and values, update multiple items at once, create copies, and much more. Mastering these methods will significantly improve your ability to work with complex data structures and write cleaner, more efficient code. Each method is optimized for specific use cases, helping you avoid writing repetitive code.

Python provides many built-in methods for working with dictionaries. These methods enable you to access, modify, and iterate over dictionary contents efficiently. Understanding when to use each method is key to writing Pythonic code.

Method Description Example
keys() Returns all keys student.keys()
values() Returns all values student.values()
items() Returns key-value pairs student.items()
update() Updates dictionary student.update({"age": 22})
copy() Creates shallow copy new_dict = student.copy()
setdefault() Gets value or sets default student.setdefault("city", "NYC")

Practical Examples

student = {"name": "Alice", "age": 20, "grade": "A"}

# Iterate over keys
for key in student.keys():
    print(key)

# Iterate over values
for value in student.values():
    print(value)

# Iterate over key-value pairs
for key, value in student.items():
    print(f"{key}: {value}")

# Update multiple items
student.update({"age": 21, "city": "Boston"})

# setdefault example
student.setdefault("country", "USA")  # Adds if doesn't exist
student.setdefault("name", "Bob")     # Doesn't change existing value

5. Dictionary Comprehensions

Introduction: Dictionary comprehensions are a concise and elegant way to create dictionaries in Python. Similar to list comprehensions, they allow you to generate dictionaries in a single line of code, making your programs more readable and often faster. This Pythonic feature is especially useful when transforming data, filtering items, or creating mappings from other iterables. Once you master comprehensions, you'll find yourself writing more expressive and efficient code.

Dictionary comprehensions provide a concise way to create dictionaries using a single line of code. They follow the syntax {key_expression: value_expression for item in iterable} and can include conditional logic for filtering items. This approach is both more readable and often faster than using traditional loops.

Basic Syntax

# {key_expression: value_expression for item in iterable}

# Create squares dictionary
squares = {x: x**2 for x in range(1, 6)}
print(squares)
# Output: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

With Conditions

# Even numbers only
even_squares = {x: x**2 for x in range(1, 11) if x % 2 == 0}
print(even_squares)
# Output: {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

# Convert list to dictionary
names = ["Alice", "Bob", "Charlie"]
name_lengths = {name: len(name) for name in names}
print(name_lengths)
# Output: {'Alice': 5, 'Bob': 3, 'Charlie': 7}

From Two Lists

keys = ["name", "age", "city"]
values = ["Alice", 20, "NYC"]

# Using zip()
student = {k: v for k, v in zip(keys, values)}
print(student)
# Output: {'name': 'Alice', 'age': 20, 'city': 'NYC'}

6. Nested Dictionaries

Introduction: Real-world data is rarely flat and simple. Nested dictionaries allow you to represent hierarchical and complex data structures, such as JSON responses from APIs, configuration files, or database records with relationships. By nesting dictionaries within dictionaries, you can model sophisticated data relationships while maintaining the fast lookup speeds that dictionaries provide. This is essential for working with modern applications that handle structured data.

Dictionaries can contain other dictionaries as values, creating nested or hierarchical structures. This is particularly useful for representing complex data like student records with multiple attributes, product catalogs with categories, or any tree-like data structure. Accessing nested values requires chaining key lookups.

Creating Nested Dictionaries

students = {
    "student1": {
        "name": "Alice",
        "age": 20,
        "grades": {"math": 90, "english": 85}
    },
    "student2": {
        "name": "Bob",
        "age": 22,
        "grades": {"math": 88, "english": 92}
    }
}

# Accessing nested values
print(students["student1"]["name"])           # Output: Alice
print(students["student1"]["grades"]["math"]) # Output: 90

Iterating Nested Dictionaries

for student_id, student_info in students.items():
    print(f"\n{student_id}:")
    for key, value in student_info.items():
        print(f"  {key}: {value}")

7. Merging Dictionaries

Introduction: Combining multiple dictionaries is a common operation in Python programming. Whether you're merging configuration settings, combining data from different sources, or updating records, Python offers several elegant methods to merge dictionaries. The method you choose depends on your Python version, whether you want to modify the original dictionary, and how you want to handle duplicate keys. Understanding these approaches helps you write cleaner, more maintainable code.

There are multiple ways to merge dictionaries in Python, each with its own use cases. The update() method modifies the original dictionary, while unpacking operators create new merged dictionaries. Python 3.9+ introduces the | operator for even more intuitive merging. When dictionaries have duplicate keys, the value from the second dictionary typically overwrites the first.

Using update() Method

dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}

dict1.update(dict2)
print(dict1)  # Output: {'a': 1, 'b': 2, 'c': 3, 'd': 4}

Using ** Unpacking (Python 3.5+)

dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}

merged = {**dict1, **dict2}
print(merged)  # Output: {'a': 1, 'b': 2, 'c': 3, 'd': 4}

Using | Operator (Python 3.9+)

dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}

merged = dict1 | dict2
print(merged)  # Output: {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# In-place merge
dict1 |= dict2
Tip: When merging dictionaries with duplicate keys, the value from the second dictionary overwrites the first.

8. Dictionary Operations

Introduction: Beyond basic access and modification, dictionaries support various operations that help you analyze and transform your data. These operations include checking the size of dictionaries, verifying membership, sorting items, and filtering based on conditions. Understanding these operations enables you to perform complex data analysis tasks efficiently. Whether you're finding the highest score, filtering valid entries, or checking for specific keys, these operations are fundamental to data processing.

Common operations you can perform on dictionaries include measuring size, checking membership, sorting by keys or values, and filtering items based on conditions. These operations leverage Python's built-in functions and methods to manipulate dictionary data effectively.

Length and Membership

student = {"name": "Alice", "age": 20, "grade": "A"}

# Get number of items
print(len(student))  # Output: 3

# Check membership (only checks keys)
print("name" in student)      # Output: True
print("Alice" in student)     # Output: False

# Check if value exists
print("Alice" in student.values())  # Output: True

Sorting Dictionaries

scores = {"Alice": 85, "Bob": 92, "Charlie": 78, "David": 95}

# Sort by keys
sorted_by_keys = dict(sorted(scores.items()))
print(sorted_by_keys)

# Sort by values
sorted_by_values = dict(sorted(scores.items(), key=lambda x: x[1]))
print(sorted_by_values)

# Sort by values (descending)
sorted_desc = dict(sorted(scores.items(), key=lambda x: x[1], reverse=True))
print(sorted_desc)

Filtering Dictionaries

scores = {"Alice": 85, "Bob": 92, "Charlie": 78, "David": 95}

# Get students with score > 80
high_scorers = {k: v for k, v in scores.items() if v > 80}
print(high_scorers)
# Output: {'Alice': 85, 'Bob': 92, 'David': 95}

9. Default Dictionaries

Introduction: One common challenge when working with dictionaries is handling missing keys. The defaultdict from Python's collections module solves this problem elegantly by providing automatic default values for missing keys. This eliminates the need for constant key existence checks and makes code cleaner and more readable. It's particularly valuable for counting, grouping, and accumulating data where you'd otherwise need to initialize values manually.

The defaultdict from the collections module provides default values for missing keys, eliminating KeyError exceptions and reducing boilerplate code. You specify a default factory function (like int, list, or set) that generates the default value when a key is accessed for the first time. This is especially useful for counting occurrences or grouping items.

from collections import defaultdict

# Create defaultdict with int (default value is 0)
word_count = defaultdict(int)

text = "hello world hello python"
for word in text.split():
    word_count[word] += 1  # No KeyError even if word doesn't exist

print(dict(word_count))
# Output: {'hello': 2, 'world': 1, 'python': 1}

# With list as default
groups = defaultdict(list)
groups["fruits"].append("apple")
groups["fruits"].append("banana")
groups["vegetables"].append("carrot")

print(dict(groups))
# Output: {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}

Common Default Types

from collections import defaultdict

# Default to int (0)
int_dict = defaultdict(int)

# Default to list ([])
list_dict = defaultdict(list)

# Default to set (set())
set_dict = defaultdict(set)

# Default to custom value
custom_dict = defaultdict(lambda: "Not Available")

10. Ordered Dictionaries

Introduction: While modern Python dictionaries (3.7+) maintain insertion order by default, the OrderedDict class from the collections module provides additional functionality for managing order. This specialized dictionary type offers methods to reorder items, which can be useful when you need to maintain a specific sequence or implement features like LRU caches. Understanding the evolution of dictionary ordering in Python helps you write version-compatible code and choose the right tool for your needs.

Since Python 3.7+, regular dictionaries maintain insertion order as part of the language specification. However, OrderedDict from the collections module remains useful for its additional methods like move_to_end(), which allows explicit reordering of items. For Python versions before 3.7, OrderedDict was essential for preserving insertion order.

from collections import OrderedDict

# Create OrderedDict
ordered = OrderedDict()
ordered["first"] = 1
ordered["second"] = 2
ordered["third"] = 3

print(ordered)
# Output: OrderedDict([('first', 1), ('second', 2), ('third', 3)])

# Move to end
ordered.move_to_end("first")
print(ordered)
# Output: OrderedDict([('second', 2), ('third', 3), ('first', 1)])

# Move to beginning
ordered.move_to_end("third", last=False)
print(ordered)
# Output: OrderedDict([('third', 3), ('second', 2), ('first', 1)])
Note: In Python 3.7+, regular dictionaries maintain insertion order, so OrderedDict is mainly useful for its additional methods like move_to_end().

11. Common Dictionary Patterns

Introduction: As you work with Python, certain patterns and idioms emerge for solving common dictionary-related problems. These patterns represent best practices developed by the Python community for tasks like inverting mappings, counting occurrences, and grouping related items. Learning these patterns will help you recognize similar problems in your own code and apply proven solutions quickly and efficiently. These are the building blocks of data processing in Python.

Useful patterns and idioms for working with dictionaries that solve common programming challenges. These include inverting dictionaries to swap keys and values, counting item frequencies using the Counter class, and grouping related data using defaultdict. Mastering these patterns makes your code more Pythonic and efficient.

Inverting a Dictionary

original = {"a": 1, "b": 2, "c": 3}

# Swap keys and values
inverted = {v: k for k, v in original.items()}
print(inverted)
# Output: {1: 'a', 2: 'b', 3: 'c'}

Counting with Dictionaries

from collections import Counter

# Count occurrences
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]

# Method 1: Using Counter
count = Counter(words)
print(count)
# Output: Counter({'apple': 3, 'banana': 2, 'cherry': 1})

# Method 2: Manual counting
count = {}
for word in words:
    count[word] = count.get(word, 0) + 1
print(count)

Grouping with Dictionaries

# Group students by grade
students = [
    {"name": "Alice", "grade": "A"},
    {"name": "Bob", "grade": "B"},
    {"name": "Charlie", "grade": "A"},
    {"name": "David", "grade": "B"}
]

from collections import defaultdict
grouped = defaultdict(list)

for student in students:
    grouped[student["grade"]].append(student["name"])

print(dict(grouped))
# Output: {'A': ['Alice', 'Charlie'], 'B': ['Bob', 'David']}

12. Performance Tips

Performance Tips:

Summary

Dictionaries are one of Python's most powerful and versatile data structures. They provide fast lookups, flexible data organization, and numerous built-in methods for manipulation. Master these concepts to write more efficient and Pythonic code!